#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
##########################################################################
#
# modBusComm.py
# Classe que s'encarrega de les comunicacions amb analitzadors d'energia
# que empren protocol MODBUS a travers del port serie en mode RTU.
#
# Inicialment desenvolupat per treballar amb analitzadors de la serie PMxxx
# de Groupe Schneider.
#
# Tomeu Capó
#

from crcTable import *

E_OK = 0
E_NOT_RESPONSE = 1 
E_CRC_ERROR = 2
E_SND_ERROR = 3 
E_CMD_ERROR = 4

READ_WORDS  = 3
READ_E_WORDS = 4
WRITE_WORD  = 6
DIAG_EXCHNG = 8
WRITE_WORDS = 16
REPOR_SLAVE = 17
IDENT_DSP   = 43

msgErrors = {E_NOT_RESPONSE: "El dispositiu no reson, ho tonare a provar",
             E_CRC_ERROR:    "La trama rebuda es erronea, error de CRC!",
	     E_SND_ERROR:    "No puc enviar la trama al dispositiu",
	     E_CMD_ERROR:    "Error de la comanda"}
			  
msgCmdError = ["Funcio invalida",
               "Adreca invalida",
	       "Valor incorrecte",
	       "Error al dispositiu esclau",
	       "L'esclau ha acceptat la peticio",
	       "Esclau ocupat",
	       "L'esclau no ha acceptat la peticio",
	       "Error de paritat de memoria"]

class modBusComm:
      def __init__(self, addrSlave, dspSerie):
          self.adrecaEsclau = addrSlave
          self.serie = dspSerie
          self.lastCmd = ''
          self.lastResponse = ''
          self.lastSize = 0
          self.lastError = E_OK
          self.lastErrorCmd = -1 
          self.logger = None

      # Metode que envia una trama en format ModBUS RTU

      def enviar(self, op, addr, size):
          retval = True

          # Si hem especificat una @ d'un registre, hem d'especifcar l'amplada
          # del mateix.

          if addr>0: 
             sndtxt  = chr(self.adrecaEsclau)
             sndtxt += chr(op)

	     sndtxt += chr((addr - 1) >> 8)
	     sndtxt += chr((addr - 1) & 0xff)

             sndtxt += chr(size >> 8)
	     sndtxt += chr(size & 0xff) 
	     self.lastSize = size
          else:
	     sndtxt = chr(self.adrecaEsclau)+chr(op)

          # Calculam el CRC de la trama construida

          crc = CRC16(sndtxt)
          sndtxt+=chr(crc >> 8)
	  sndtxt+=chr(crc & 0xFF)

          try:
             self.lastCmd = op 
             self.logger.debug("[ TX ] "+' '.join(["%(vc)02X" % {"vc": ord(c)} for c in sndtxt])) 
             self.serie.write(sndtxt)
	     self.lastError = E_OK
          except:
	     self.lastError = E_SND_ERROR
             retval = False
             

          return(retval)           

      # Metode que ens permet rebre una trama ModBUS RTU i extreure informacio

      def rebre(self):
          retval = True
	  respHead = self.serie.read(3)
          frameRx = respHead

          # Si a la capsalera hi ha res. Recordam que el metode read
          # te un timeout. Normalment basta si l'esclau esta funcionant correctament.

	  if len(respHead)>2:

             # Determinam si es la resposta de la peticio feta anteriorment
             # hem de pensar que primer enviam una consulta i ens possam amb mode
             # d'escolta.

	     retCode = ord(respHead[1])
	     if (retCode == self.lastCmd):
		 lenData = ord(respHead[2])
                 i=0

	   	 self.lastResponse = []
		 self.lastResponseB = ''

                 # Extreim la informacio de la trama. Tenim dos atributs:
                 # lastResponseB: Conte la informacio en format cru.
                 # lastResponse: Conte una llista dels valors.

                 while i<lenData:
		       resp = self.serie.read(2)
		       frameRx += resp 
                       value = (ord(resp[0]) << 8) | ord(resp[1])
		       self.lastResponseB+=resp
	               self.lastResponse.append(value)
		       i+=2

                 # Llegim els dos bytes de la coa (CRC)

		 respTail = self.serie.read(2)
		 frameRx += respTail

                 self.logger.debug("[ RX ] "+' '.join(["%(vc)02X" % {"vc": ord(c)} for c in frameRx]))

                 # Calculam el CRC en base a la informacio rebuda fins ara
                 # i verificam que sigui el mateix que rebem del esclau.

                 crcRx = (ord(respTail[0]) << 8) | ord(respTail[1])
	         crcCa = CRC16(frameRx[:-2])

                 if crcRx != crcCa:
	            retval = False
    		    self.lastError = E_CRC_ERROR
                 else:
		    self.lastError = E_OK
		    self.lastErrorCmd = -1 
             else:
	         retval=False
	         self.lastError = E_CMD_ERROR
	         self.lastErrorCmd = 0 #ord(resp[2])-1

          else:
	     retval=False
             self.lastError = E_NOT_RESPONSE

          return(retval)

      def msgError(self):
          msgCmdErr = ""
          if self.lastError>0:
	     if self.lastErrorCmd>=0:
	        msgCmdErr = "("+msgCmdError[self.lastErrorCmd]+")"

             return(msgErrors[self.lastError]+" "+msgCmdErr)     
          else:
             return('')

  
